package com.ycxh.plugin;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

/**
 * 分页插件
 * 官网：https://mybatis.org/mybatis-3/zh/configuration.html#plugins
 */
@Intercepts({
        // @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
        // 拦截interface Executor接口里面的方法，由方法签名(方法名+参数列表)确定
        // <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyPageInterceptor implements Interceptor {

    // 分页插件是否可用
    private Boolean isEnable;

    // https://blog.csdn.net/qq_20051535/article/details/107450320
    // @Override is not allowed when implementing interface method
    // jdk1.5会报错
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (isEnable) { // 插件可用
            Method method = invocation.getMethod();
            // 调用查询方法时传递的方法参数
            Object[] args = invocation.getArgs();

            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1]; // 用户传的参数
            RowBounds rowBounds = (RowBounds) args[2];
            // java.lang.ArrayIndexOutOfBoundsException: 4
            // ResultHandler resultHandler = (ResultHandler) args[4];
            ResultHandler resultHandler = (ResultHandler) args[3];

            PageParam pageParam = null;
            String paramKey = "";
            if (parameter instanceof PageParam) { // 单个参数
                pageParam = (PageParam) parameter;
            } else if (parameter instanceof HashMap) {
                // 多个参数，mybatis会默认变成一个map结构
                HashMap<String, Object> paramMap = (HashMap) parameter;
                for (String key : paramMap.keySet()) {
                    Object value = paramMap.get(key);

                    if (value instanceof PageParam) {
                        pageParam = (PageParam) parameter;
                        paramKey = key + ".";
                    }
                }
            }

            List<Object> resultList = Collections.EMPTY_LIST;
            int total = 0;
            if (pageParam != null) {
                int curPage = pageParam.getCurPage();
                int pageRow = pageParam.getPageRow();

                // 得到一个绑定的sql语句对象
                BoundSql boundSql = ms.getBoundSql(parameter);
                // 需要分页，首先要查一下数据库总共有多少条数据，用于计算页数
                total = queryTotal(ms, parameter, boundSql);

                // 总条数>0，表示数据库有数据
                if (total > 0) {
                    // 查询当前页数据前，需要对sql语句进行拦截
                    BoundSql newBoundSql = Dialect.getBoundSql(ms, pageParam.getOffSet(), boundSql, paramKey);

                    BoundSqlSqlSource newSqlSource = new BoundSqlSqlSource(newBoundSql);
                    MappedStatement newMS = newMappedStatement(ms, newSqlSource);

                    args[0] = newMS;

                    // 查当前页的数据
                    resultList = (List) invocation.proceed();
                }
            }
            return new PageList(resultList, pageParam.getCurPage(), pageParam.getPageRow(), total);
        }

        // 分页插件不可用，直接调用原始的方法即可
        return invocation.proceed();
    }

    // 查询总条数
    private int queryTotal(MappedStatement ms, Object parameter, BoundSql boundSql) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("select count(*) from (");
        stringBuffer.append(boundSql.getSql());
        stringBuffer.append(") t");

        // 去查询
        Connection connection = null;
        // 预编译语句
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        int total = 0;

        try {
            connection = ms.getConfiguration().getEnvironment().getDataSource().getConnection();
            preparedStatement = connection.prepareStatement(stringBuffer.toString());
            // 设置参数
            DefaultParameterHandler parameterHandler = new DefaultParameterHandler(ms, parameter, boundSql);
            parameterHandler.setParameters(preparedStatement);

            // 执行查询
            resultSet = preparedStatement.executeQuery();

            if (resultSet.next()) {
                total = resultSet.getInt(1);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return total;
    }

    /**
     * 构建一个MappedStatement对象
     * 从mybatis拷贝过来的
     *
     * @param ms
     * @param newSqlSource
     * @return
     */
    public static MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder =
                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    // 定义一个内部辅助类，作用是包装sq
    public static class BoundSqlSqlSource implements SqlSource {
        private final BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

    /**
     * 产生一个动态代理对象
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 初始化mybatis-config.xml文件的时候会执行该方法，加载属性配置文件信息
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        // 局部变量名可以和全局变量名(类变量)相同
        String isEnable = properties.getProperty("isEnable");
        System.out.println(isEnable);
        this.isEnable = Boolean.parseBoolean(isEnable);
    }

}
